<?php
/**
 * Parcl
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://parcl.com/license
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@parcl.com so we can send you a copy immediately.
 *
 * @category parcl
 * @package parcl-core
 * @copyright Copyright (c) 2006-2011 Mikael Knutsson (http://mikn.me)
 * @license http://parcl.com/license New BSD License
 */
namespace Parcl\Core;
/***
 * Defining root dir and a debug level, if debug = 0, it means we're running in a
 * production environment. The other numbers are reserved for different levels of
 * debugging. The PARCL_EXEC constant is defined to prevent outside access to
 * script components, even if using another script.
 * Yet another safety measure. (YASM) - ha. ha.
 *
 * FIXME: Need to comment all code!
 */

define('ROOT_DIR', './');
define('PARCL_EXEC', 1);

$constructs = array(
		'interfaces.php',
		'registry.php',
		'events.php',
		'exceptions.php',
		'db.php',
		'debug.php',
		'modules.php',
		'sessions.php',
		'query.php',
	);

require ROOT_DIR . 'confparcl.php';
foreach($constructs as $construct) {
	require CONST_DIR . $construct;
}
$registry = Registry::GetInstance();
require CONST_DIR . 'queries/' . $registry->conf->dbType . '.php';
# We don't want any potential globals, do we?
unset($registry, $constructs);

# Handing over debug level.
new watchdog(DEBUG_LEVEL);

/**
 * Purpose	 : To make error-handling easier by only having to call on the
				 wrapper class within the try{} block.
 * Description : The actual wrapper class, it runs the instantiation and execution
				 of all classes except the debug class and the Registry class.
 * - Construct -
 * Input	   : None.
 * Description : Instanciates the DB module straight into the Registry, adds the
				 general events, registers the debug event registar and then triggers
				 the first event.
 * Errors	  : No error conditions have been defined.
 * Assumptions : That the Registry class is available.
 */

final class Parcl extends Events {

	public function __construct() {
		parent::__construct();
		$registry = Registry::GetInstance();
		# Instantiate the DB class into the registry and fetch the template path.
		$registry->Register('DB', new parclDB());
		$registry->Register('templatePath', TEMPLATE_DIR . $registry->conf->defaultTemplate);

		# Define the autoloading method.
		spl_autoload_register(array(new Modules(), 'AutoloadModules'));

		$this->registeredEvents->AddGeneralEvents($this->events);
		$this->AddEventHandler('P:STARTUP', array('watchdog', 'EventRegistar'));
		$this->triggerEvent('P:STARTUP');
		
		$sh = new DbSessions('parclSession', 'nocache', 'sessions');
        session_set_save_handler(array($sh, 'Open'), array($sh, 'Close'), array($sh, 'Read'), array($sh, 'Write'), array($sh, 'Destroy'), array($sh, 'GarbageCollect'));

		watchdog::Log('Starting parcl.', LogLevel::INFORMATIONAL);
	}

	/**
	 * Input	   : None.
	 * Output	  : None.
	 * Description : This function sets the page headers to the correct value,
					 depending on if the client explicitly can handle xhtml+xml or not.
	 * Errors	  : None.
	 * Assumptions : That the Registry class is defined.
	 * Dev Notes   : This is going to be a class to which you can dynamically add
					 headers on a page basis or a site basis.
	 */

	public function Headers() {
		$registry = Registry::GetInstance();
		$registry->Register('clientXML', (bool)
		(@strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false ? true : false));
		if($registry->Get('clientXML') !== false) {
			header('Content-Type: application/xhtml+xml');
		}
		else {
			header('Content-Type: text/html');
		}
	}

	/**
	 * Method for fetching and basic error checking on the layout.
	 *
	 * @param Registry $registry
	 * @param string $page # Which layout to fetch.
	 * @return Array representing the layout.
	 */

	private function getLayout(Registry $registry, $page) {
		$layouts = ConstructQueries::GetLayout($registry, $page);
		$layoutCount = count($layouts);
		if($layoutCount > 1) {
			throw new GeneralException('Too many rows returned by the GetLayout query.',
									GeneralException::ROW_COUNT);
		}
		if($layoutCount < 1) {
			throw new ContentException('Page cannot be found', ContentException::PAGE_NOT_FOUND);
		}
		$output = $layouts[0];
		return $output;
	}

	/**
	 * Input	   : $registry: An instance of the Registry class. Enforced through type hinting.
					 $inputContent: The 'contents' field returned by the getLayout() function.
	 * Output	  : An associative array describing the main content of the page.
	 * Description : This function finds out what module that is composing the main
					 content, instanciates it and runs it to get the appropriate
					 data for template processing.
	 * Errors	  : No error conditions have been specified as of yet.
	 * Assumptions : That $inputContent is the 'contents' field of the layouts table
					 and that the 'contents' field is properly formed.
					 That the module exists and returns a properly formed array.
	 * Dev Notes   : This function currently accepts the 'content' field to define
					 an arbitrary number of modules composing the main content of
					 the page, which should not be the case.
	 */

	private function getContent($inputContent) {
		$contents = json_decode($inputContent, true);
		foreach($contents as $content => $args) {
			$contentInstance = new $content();
			$contCollection[$content] = call_user_func_array(array($contentInstance, 'Execute'), $args);
		}
		return $contCollection;
	}

	/**
	 * Input	   : $inputParcels: The 'parcels' field returned by the getLayout() function.
	 * Output	  : An associative array describing the widgets on the page.
	 * Description : This function grabs parcel specific data from the modules,
					 instanciates them and runs them to get the appropriate return
					 data for template processing.
	 * Errors	  : No error conditions have been specified as of yet.
	 * Assumptions : That $inputParcels is the 'parcels' field of the layouts table
					 and that the 'parcels' field is properly formed.
					 That the module exists and returns a properly formed array.
	 * Dev Notes   : This module doesn't pay attention to either the parcel order,
					 nor can you call on the same module more than once at the moment.
	 */

	private function getParcels($inputParcels) {
		$parcelAreas = json_decode($inputParcels, true);
		$prclCollection = array();
		if(!count($parcelAreas) < 1) {
			foreach($parcelAreas as $area => $parcels) {
				foreach($parcels as $parcel => $args) {
					$prcl = new $parcel();
					$prclCollection[$area][$parcel] = call_user_func_array(array($prcl, 'Execute'), $args);
				}
			}
		}
		return $prclCollection;
	}

	/**
	 * Input	   : None.
	 * Output	  : The page to be displayed in a string for easy printing.
	 * Description : This is the main function of the wrapper class, it contains
					 most events and calls upon all appropriate functions to
					 generate the page in question.
	 * Errors	  : Errors are thrown if the 'contents' or 'widgets' index of the
					 $layout array isn't index of a string.
	 * Assumptions : That the Registry class is available, that the DB class is
					 called parclDB and is available and that both the XMLDriver and
					 the XSLTemplates modules are available.
	 * Dev Notes   : The error checks are more than inconclusive and the template
					 parameters should be in its own Collection class for easy
					 implementation of multi-lingual support, et cetera.
	 */

	public function Execute($page = null) {
		$registry = Registry::GetInstance();
		$s = $registry->conf->pageSwitch;
		$a = $registry->conf->ajaxSwitch;
		$ajaxRequest = false;

		session_start();
		$_SESSION['loggedIn'] = false;
		$_SESSION['userName'] = '';
    	$_SESSION['lastActivity'] = time();

		if(isset($_GET[$s], $_GET[$a])) {
			throw new GeneralException("Malformed page query. Both '$s' and '$a' were set " .
									"with values $_GET[$s] and $_GET[$a] respectively." ,
									GeneralException::UNAUTHACC);
		}
		/* Checking if we get a content error from the script execution or if we're
		 * getting an ajax request instead of a proper page request. */
		if(is_null($page)) {
			if(isset($_GET[$a])) {
				$currentPage = (string) $_GET[$a];
				$ajaxRequest = true;
			}
			else {
				$currentPage = (string) (isset($_GET[$s]) ? $_GET[$s] : $registry->conf->startPage);
			}
		}
		else {
			$currentPage = $page;
		}

		$registry->Current->page = $currentPage;

		$layout = $this->getLayout($registry, $currentPage);
		$this->triggerEvent('P:LAYOUT_LOADED'); // Removed passing $layout for security.

		$registry->Current->pageID = $layout['id'];

		$pageData['content'] = $this->getContent($layout['contents']);
		$this->triggerEvent('P:CONTENT_LOADED', array('content' => &$pageData['content']));

		$pageData['parcels'] = $this->getParcels($layout['parcels']);
		$this->triggerEvent('P:PARCELS_LOADED', array('parcels' => &$pageData['parcels']));

		if($ajaxRequest) {
			$master = $this->makeReply($pageData);
		}
		else {
			$master = $this->makePage($registry, $pageData);
		}
		return $master;
	}

	/**
	 * The encapsulating method for handling AJAX requests.
	 *
	 * @param array/object $pageData
	 * @return string (json array)
	 *
	 * TODO: Decide how to deal with AJAX requests properly.
	 */

	private function makeReply($pageData) {
		return json_encode($pageData);
	}

	/**
	 * Method for running the standard template engine over the content array.
	 *
	 * @param Registry $registry
	 * @param array/object $pageData
	 * @return string (proper XHTML)
	 */

	private function makePage(Registry $registry, $pageData) {
		$tP = new $registry->conf->defaultEngine();
		$headers = headers::GetInstance();

		//$registry->Register('clientXML', (@strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false ? true : false));
		if($registry->Get('clientXML')) {
			$headers->setContentType('application/xhtml+xml');
		}
		else {
			$headers->setContentType('text/html');
		}

		$contentType = $headers->getContentType();
		//$headers->Send();

		$templateParams =
				  array('pageTitle' => 'parcl - ' . $pageData['content']['articles']['title'],
						 'contentType' => $contentType,
						 'rootPath' => $registry->conf->rootURL,
						 'templatePath' => TEMPLATE_DIR . $registry->conf->defaultTemplate,
						);
		$extras = array();
		$this->triggerEvent('P:PAGE_PREPARED', array('pageData' => &$pageData,
													   'templateParams' => &$templateParams,
													   'extraTemplates' => &$extras));

		$page = $tP->Execute($pageData, $templateParams, $extras);

		$this->triggerEvent('P:PAGE_GENERATED', array('page' => &$page));
		return $page;
	}

	public function __destruct() {
		session_commit();
		$this->triggerEvent('P:SHUTDOWN');
	}
}

/***
 * The error check block, this is in for an extension and integration with the debug
 * class' different levels, etc.
 */

try {
	$index = new Parcl();
	$index->Headers();
	echo $index->Execute();
	unset($index); // Need to unset so that the logging works properly.
}
catch(ContentException $e) {
	echo $index->Execute($e->getCode());
	unset($index); // Need to unset so that the logging works properly.
}
catch(Exception $e) {
	echo <<<_EOF_
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
_EOF_;
	echo '<h2>' . get_class($e) . "</h2>\n";
	echo '<h3>' . $e->getMessage() . ' (Error Code: ' . $e->getCode() . ")</h3>\n\n";
	echo '<b>file:</b> ' . $e->getFile() . "<br />\n";
	echo '<b>line:</b> ' . $e->getLine() . "<br />\n";
	foreach($e->getTrace() as $line => $lineInfo) {
		echo '<h4>' . $line . '</h4>';
		foreach($lineInfo as $stuff => $info) {
			if($stuff !== 'args')
				echo '<b>' . $stuff . ':</b> ' . $info . '<br />';
			else {
				echo '<h4>' . $stuff . ':</h4>';
				foreach($info as $args)
					var_dump($args);
			}
		}
	}
	echo '</body></html>';
	die;
}